﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WINADM.Data;
using log4net;
using System.Data.Objects;
using SNNI = System.Net.NetworkInformation;
using System.Net;
using WINADM.Server.WMI;
using System.IO;
namespace WINADM.Server
{
    public class Controller : IDisposable
    {
        ILog _logger;
        IEntityRepository _entityRepository;
        ISystemInfo _systemInfo;
        IWMIHelperFactory _wmiHelperFactory;
        IPerformanceMonitor _performanceMonitor;

        static object syncObject = new object();
        public Controller(IEntityRepository entityRepository, ILog logger, ISystemInfo systemInfo)
        {
            this._entityRepository = entityRepository;
            this._logger = logger;
            this._systemInfo = systemInfo;
            this._wmiHelperFactory = new WMIHelperFactory(_logger);
        }

        public Controller(IEntityRepository entityRepository, ILog logger, ISystemInfo systemInfo, IWMIHelperFactory wmiHelperFactory)
        {
            this._entityRepository = entityRepository;
            this._logger = logger;
            this._systemInfo = systemInfo;
            this._wmiHelperFactory = wmiHelperFactory;
        }

        public Controller(IEntityRepository entityRepository, ILog logger, ISystemInfo systemInfo, IWMIHelperFactory wmiHelperFactory, IPerformanceMonitor performanceMonitor)
        {
            this._entityRepository = entityRepository;
            this._logger = logger;
            this._systemInfo = systemInfo;
            this._wmiHelperFactory = wmiHelperFactory;
            this._performanceMonitor = performanceMonitor;
        }

        public Controller()
        {
            this._logger = LogManager.GetLogger(typeof(Controller));
            ObjectContext winadmDataContext = new WINADMEntities();
            winadmDataContext.CommandTimeout = 180;
            this._entityRepository = (IEntityRepository)winadmDataContext;
            this._systemInfo = new SystemInfo();
            this._wmiHelperFactory = new WMIHelperFactory(_logger);
        }

        public bool RegisterComputer()
        {
            try
            {
                lock (syncObject)
                {
                    _logger.Info("Retrieving registeration info for computer " + _systemInfo.MachineName + "...");

                    var computers = from computer in _entityRepository.Computers where computer.ComputerName == _systemInfo.MachineName select computer;

                    if (computers.Count<Computer>() == 0)
                    {
                        _logger.Info(_systemInfo.MachineName + " is not registered. Registering for first time.");
                        _logger.Debug("Following registered computers found...");
                        foreach (var computer in _entityRepository.Computers)
                        {
                            _logger.Debug(computer.ComputerName);
                        }

                        //Create new computer object
                        Computer newComputer = new Computer();
                        newComputer.ComputerName = _systemInfo.MachineName;
                        newComputer.LastHeartBeat = _systemInfo.TimeStamp;

                        //add new computer object to repository
                        _entityRepository.Computers.AddObject(newComputer);

                        if (_entityRepository.SaveChanges() == 1)
                        {
                            _logger.Info(_systemInfo.MachineName + " registered successfully.");
                        }
                        else
                        {
                            _logger.Info(_systemInfo.MachineName + " registeration unsuccessfull.");
                            return false;
                        }

                    }
                    else
                    {
                        _logger.Info(_systemInfo.MachineName + " is already registered. Updating LastHeartBeat time.");
                        foreach (var computer in computers)
                        {
                            computer.LastHeartBeat = _systemInfo.TimeStamp;
                        }
                        if (_entityRepository.SaveChanges() == 1)
                        {
                            _logger.Info(_systemInfo.MachineName + " LastHeartBeat time updated successfully.");
                        }
                        else
                        {
                            _logger.Info(_systemInfo.MachineName + " LastHeartBeat time update unsuccessfull.");
                            return false;
                        }

                    }

                    return true;
                }
            }
            catch (Exception ex)
            {
                _logger.Error("Exception occured while registering computer.", ex);
            }
            return false;
        }

        public bool UpdateNetworkInterfaceInformation()
        {
            try
            {
                _logger.Info("Updating Network Information for computer " + _systemInfo.MachineName + "...");
                lock (syncObject)
                {
                    Computer computer = _entityRepository.Computers.Single(x => x.ComputerName == _systemInfo.MachineName);
                    SNNI.NetworkInterface[] networkInterfaces = _systemInfo.GetNetworkInterfaces();

                    for (int i = 0; i < networkInterfaces.Length; i++)
                    {
                        computer = _entityRepository.Computers.Single(x => x.ComputerName == _systemInfo.MachineName);
                        if (computer.NetworkInterfaces.Count == 0)
                        {
                            AddNewNetworkInterface(computer, networkInterfaces[i]);
                        }
                        else
                        {
                            NetworkInterface existingNetworkInterface = computer.NetworkInterfaces.SingleOrDefault(x => x.Id == networkInterfaces[i].Id);
                            if (existingNetworkInterface == null)
                            {
                                AddNewNetworkInterface(computer, networkInterfaces[i]);
                            }
                            else
                            {
                                UpdateNetworkInterface(computer, networkInterfaces[i], existingNetworkInterface);
                            }

                        }
                    }


                    _logger.Info("Network Information Updated successfully....");
                    return true;
                }

            }
            catch (Exception ex)
            {
                _logger.Error("Exception occured while updating network interface information.", ex);
            }



            return false;
        }

        public bool UpdateProcessInformation()
        {
            try
            {
                lock (syncObject)
                {
                    _logger.Info("Updating Process Information for computer " + _systemInfo.MachineName + "...");
                    Computer computer = _entityRepository.Computers.Single(x => x.ComputerName == _systemInfo.MachineName);
                    _entityRepository.DeleteProcesses(computer.Id);
                    IWin32ProcessHelper win32ProcessHelper = _wmiHelperFactory.CreateWin32ProcessHelper();
                    IList<Win32Process> win32processList = win32ProcessHelper.GetAllProcesses();
                    foreach (Win32Process win32Process in win32processList)
                    {
                        _entityRepository.AddProcessEntry(computer.Id,
                                                           (int?)win32Process.ProcessId,
                                                           win32Process.Caption,
                                                           win32Process.Description,
                                                           win32Process.CommandLine,
                                                           win32Process.ExecutablePath,
                                                           win32Process.Name,
                                                           (int?)win32Process.ParentProcessId,
                                                           (long?)win32Process.KernelModeTime,
                                                           (int?)win32Process.PageFileUsage,
                                                           (int?)win32Process.Priority,
                                                           (int?)win32Process.ThreadCount,
                                                           (long?)win32Process.PrivatePageCount,
                                                           (long?)win32Process.UserModeTime,
                                                           (long?)win32Process.VirtualSize,
                                                           win32Process.CreationDate);
                    }
                    win32processList.Clear();
                    _logger.Info("Process Information updated successfully for computer " + _systemInfo.MachineName + "...");
                    return true;
                }
            }
            catch (Exception ex)
            {
                _logger.Error("Exception occured while updating process information.", ex);
            }
            return false;
        }

        public bool UpdateEventLogEntries()
        {
            try
            {
                lock (syncObject)
                {
                    _logger.Info("Updating event log Information for computer " + _systemInfo.MachineName + "...");
                    EventLog lastLog = _entityRepository.GetLatestEventLogEntry().FirstOrDefault();
                    DateTime? lastLogDate = _systemInfo.TimeStamp.AddMonths(-1);
                    Computer computer = _entityRepository.Computers.Single(x => x.ComputerName == _systemInfo.MachineName);

                    if (lastLog != null)
                    {
                        lastLogDate = lastLog.TimeWritten;
                    }
                    return UpdateEventLogEntries(lastLogDate, computer);
                }

            }
            catch (Exception ex)
            {
                _logger.Error("Exception occured while updating event log information.", ex);
            }
            return false;
        }

        public bool UpdateDriveInformation()
        {
            try
            {
                lock (syncObject)
                {
                    _logger.Info("Updating drive Information for computer " + _systemInfo.MachineName + "...");
                    Computer computer = _entityRepository.Computers.Single(x => x.ComputerName == _systemInfo.MachineName);
                    if (computer.Drives.Count != 0)
                    {
                        computer.Drives.Clear();
                        _entityRepository.SaveChanges();

                    }
                    DriveInfo[] drives = _systemInfo.GetDrives();
                    foreach (DriveInfo drive in drives)
                    {
                        Drive computerDrive = new Drive();
                        computerDrive.ComputerID = computer.Id;

                        if (drive.IsReady)
                        {
                            computerDrive.AvailableFreeSpace = drive.AvailableFreeSpace;
                            computerDrive.TotalFreeSpace = drive.TotalFreeSpace;
                            computerDrive.TotalSize = drive.TotalSize;
                            computerDrive.DriveFormat = drive.DriveFormat;
                            computerDrive.VolumeLabel = drive.VolumeLabel;
                        }
                        computerDrive.DriveTypeId = (byte)drive.DriveType;
                        computerDrive.Name = drive.Name;
                        computer.Drives.Add(computerDrive);
                    }
                    if (_entityRepository.SaveChanges() == 0)
                    {
                        _logger.Error("Unable to save drive information to database.");
                        return false;
                    }
                    _logger.Info("Drive Information updated successfully for computer " + _systemInfo.MachineName + "...");
                    return true;
                }

            }
            catch (Exception ex)
            {
                _logger.Error("Exception occured while updating drive information.", ex);
            }
            return false;
        }

        public bool UpdateComputerPerformanceStats()
        {
            try
            {
                lock (syncObject)
                {
                    _logger.Debug("Updating Performance Data for computer " + _systemInfo.MachineName + "...");
                    if (_performanceMonitor == null)
                    {
                        Computer computer = _entityRepository.Computers.Single(x => x.ComputerName == _systemInfo.MachineName);
                        _performanceMonitor = new PerformanceMonitor(computer, _logger, _systemInfo);
                    }
                    CPUUsage cpuUsage = _performanceMonitor.UpdateCPUUsageData();
                    _entityRepository.AddCPUUsageEntry(cpuUsage.ComputerId, cpuUsage.Time, cpuUsage.Value);

                    RAMUsage ramUsage = _performanceMonitor.UpdateRAMUsageData();
                    _entityRepository.AddRAMUsageEntry(ramUsage.ComputerId, ramUsage.Time, ramUsage.Value);

                    _logger.Debug("Performance data updated successfully for computer " + _systemInfo.MachineName + "...");
                    return true;
                }

            }
            catch (Exception ex)
            {
                _logger.Error("Exception occured while updating performance information information.", ex);
            }
            return false;
        }

        public bool UpdateServiceInformation()
        {
            try
            {
                lock (syncObject)
                {
                    _logger.Debug("Updating Service Information for computer " + _systemInfo.MachineName + "...");
                    Computer computer = _entityRepository.Computers.Single(x => x.ComputerName == _systemInfo.MachineName);
                    _entityRepository.DeleteServices(computer.Id);
                    UpdateServiceInformation(computer);
                    UpdateDependentServiceInformation(computer);
                    _logger.Info("Service Information updated successfully for computer " + _systemInfo.MachineName + "...");
                    return true;
                }
            }
            catch (Exception ex)
            {
                _logger.Error("Exception occured while updating service information information.", ex);
            }
            return false;
        }

        private void UpdateServiceInformation(Computer computer)
        {
            IWin32ServiceHelper win32ServiceHelper = _wmiHelperFactory.CreateWin32ServiceHelper();
            IList<Win32Service> win32services = win32ServiceHelper.GetAllServices();
            foreach (Win32Service win32Service in win32services)
            {
                _entityRepository.AddServiceEntry(win32Service.AcceptPause,
                                                  win32Service.AcceptStop,
                                                  win32Service.DisplayName,
                                                  win32Service.Description,
                                                  win32Service.Name,
                                                  (short?)win32Service.ServiceType,
                                                  (byte?)win32Service.State,
                                                  computer.Id,
                                                  (byte?)win32Service.StartMode,
                                                  win32Service.DesktopInteract,
                                                  win32Service.InstallDate,
                                                  win32Service.PathName,
                                                  (int?)win32Service.ProcessId,
                                                  win32Service.StartName);
            }
            win32services.Clear();
        }

        private void UpdateDependentServiceInformation(Computer computer)
        {
            IWin32ServiceHelper win32ServiceHelper = _wmiHelperFactory.CreateWin32ServiceHelper();
            IList<Win32DependentService> win32Dependentservices = win32ServiceHelper.GetAllDependentService();
            foreach (Win32DependentService win32DependentService in win32Dependentservices)
            {
                _entityRepository.AddDependentServiceEntry(computer.Id,
                                                            win32DependentService.ServiceName,
                                                            win32DependentService.DependentServiceName);
            }
            win32Dependentservices.Clear();
        }

        private bool UpdateEventLogEntries(DateTime? lastLogDate, Computer computer)
        {
            IWin32EventLogHelper win32EventLogHelper = _wmiHelperFactory.CreateWin32EventLogHelper();

            IList<Win32EventLog> win32EventEntries = win32EventLogHelper.GetAllEventLogEntries((DateTime)lastLogDate);
            foreach (Win32EventLog eventLogEntry in win32EventEntries)
            {
                _entityRepository.AddEventLogEntry(computer.Id, eventLogEntry.Logfile, (int?)eventLogEntry.RecordNumber, eventLogEntry.CategoryString,
                                                    (int?)eventLogEntry.EventIdentifier, (byte)eventLogEntry.EventType, eventLogEntry.Message,
                                                    eventLogEntry.SourceName, eventLogEntry.TimeGenerated, eventLogEntry.TimeWritten, eventLogEntry.User);
            }
            _logger.Info("Eventlog Information updated successfully for computer " + _systemInfo.MachineName + "...");
            return true;
        }

        private bool AddNewNetworkInterface(Computer computer, SNNI.NetworkInterface networkInterface)
        {
            NetworkInterface newNetworkInterface = new NetworkInterface();
            newNetworkInterface.ComputerId = computer.Id;
            newNetworkInterface.Id = networkInterface.Id;
            newNetworkInterface.Name = networkInterface.Name;
            newNetworkInterface.PhysicalAddress = networkInterface.GetPhysicalAddress().ToString();
            newNetworkInterface.Description = networkInterface.Description;
            newNetworkInterface.TypeId = (short)networkInterface.NetworkInterfaceType;
            newNetworkInterface.OperationalStatusId = (byte)networkInterface.OperationalStatus;
            SNNI.IPInterfaceProperties ipProperties = networkInterface.GetIPProperties();

            if (networkInterface.OperationalStatus == SNNI.OperationalStatus.Up && networkInterface.NetworkInterfaceType != SNNI.NetworkInterfaceType.Loopback)
            {
                newNetworkInterface.IPAddress = ipProperties.UnicastAddresses[0].Address.ToString();
                newNetworkInterface.DHCPServer = ipProperties.DhcpServerAddresses[0].ToString();
                newNetworkInterface.DefaultGateway = ipProperties.GatewayAddresses[0].Address.ToString();
            }

            foreach (IPAddress dnsServerIP in ipProperties.DnsAddresses)
            {
                DNSServer dnsServer = new DNSServer();
                dnsServer.ComputerId = computer.Id;
                dnsServer.NetworkInterfaceId = newNetworkInterface.Id;
                dnsServer.Address = dnsServerIP.ToString();
                newNetworkInterface.DNSServers.Add(dnsServer);
            }

            foreach (IPAddress winsServerIP in ipProperties.WinsServersAddresses)
            {
                if (winsServerIP.ToString() != "0.0.0.0")
                {
                    WINSServer winsServer = new WINSServer();
                    winsServer.ComputerId = computer.Id;
                    winsServer.NetworkInterfaceId = newNetworkInterface.Id;
                    winsServer.Address = winsServerIP.ToString();
                    newNetworkInterface.WINSServers.Add(winsServer);
                }
            }

            _entityRepository.NetworkInterfaces.AddObject(newNetworkInterface);
            if (_entityRepository.SaveChanges() == 0)
            {
                _logger.Error("Unable to save network interace record to database.");
                return false;
            }
            return true;
        }

        private bool UpdateNetworkInterface(Computer computer, SNNI.NetworkInterface networkInterface, NetworkInterface existingNetworkInterface)
        {
            existingNetworkInterface.OperationalStatusId = (byte)networkInterface.OperationalStatus;
            SNNI.IPInterfaceProperties ipProperties = networkInterface.GetIPProperties();

            if (networkInterface.OperationalStatus == SNNI.OperationalStatus.Up && networkInterface.NetworkInterfaceType != SNNI.NetworkInterfaceType.Loopback)
            {
                existingNetworkInterface.IPAddress = ipProperties.UnicastAddresses[0].Address.ToString();
                existingNetworkInterface.DHCPServer = ipProperties.DhcpServerAddresses[0].ToString();
                existingNetworkInterface.DefaultGateway = ipProperties.GatewayAddresses[0].Address.ToString();
            }
            else
            {
                existingNetworkInterface.IPAddress = null;
                existingNetworkInterface.DHCPServer = null;
                existingNetworkInterface.DefaultGateway = null;
            }

            DNSServer[] dnsservers = existingNetworkInterface.DNSServers.ToArray<DNSServer>();

            WINSServer[] winsservers = existingNetworkInterface.WINSServers.ToArray<WINSServer>();

            for (int i = 0; i < dnsservers.Length; i++)
            {
                _entityRepository.DNSServers.DeleteObject(dnsservers[i]);
                _entityRepository.SaveChanges();
            }

            for (int i = 0; i < winsservers.Length; i++)
            {
                _entityRepository.WINSServers.DeleteObject(winsservers[i]);
                _entityRepository.SaveChanges();
            }



            foreach (IPAddress dnsServerIP in ipProperties.DnsAddresses)
            {
                DNSServer dnsServer = new DNSServer();
                dnsServer.ComputerId = computer.Id;
                dnsServer.NetworkInterfaceId = existingNetworkInterface.Id;
                dnsServer.Address = dnsServerIP.ToString();
                existingNetworkInterface.DNSServers.Add(dnsServer);
            }

            foreach (IPAddress winsServerIP in ipProperties.WinsServersAddresses)
            {
                WINSServer winsServer = new WINSServer();
                if (winsServerIP.ToString() != "0.0.0.0")
                {
                    winsServer.ComputerId = computer.Id;
                    winsServer.NetworkInterfaceId = existingNetworkInterface.Id;
                    winsServer.Address = winsServerIP.ToString();
                    existingNetworkInterface.WINSServers.Add(winsServer);
                }
            }

            if (_entityRepository.SaveChanges() == 0)
            {
                _logger.Error("Unable to save network interace record to database.");
                return false;
            }
            return true;
        }

        public bool RemoveComputer()
        {
            _logger.Info("Retrieving registeration info for computer " + _systemInfo.MachineName + "...");

            var computers = from computer in _entityRepository.Computers where computer.ComputerName == _systemInfo.MachineName select computer;

            if (computers.Count<Computer>() != 0)
            {
                foreach (Computer computer in computers)
                {
                    _entityRepository.DeleteComputer(computer.ComputerName);
                }
            }

            return false;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            _logger = null;
            if (_entityRepository != null)
                ((IDisposable)_entityRepository).Dispose();
            _systemInfo = null;
        }
    }
}
